/**
 * SHACrypt graphical user interface
 *
 * SHACrypt - Encrypt files using cryptographic hash functions
 * Copyright (C) 2010 LlamaSlayers
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @since 1.2.1
 * @version 1.2.2
 */

#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixdata.h>
#include <stdio.h>
#include <string.h>
#include "shacrypt.h"
#include "glade.h"
#include "shacrypt-icon.h"

static const char identifier[3] = "\xA7\x09\xC3";

GtkBuilder* builder;

/* Encryption */
G_MODULE_EXPORT void button_pressed_encrypt( GtkWidget* button, gpointer data ) {
    GtkWidget* step1;
    step1 = GTK_WIDGET( gtk_builder_get_object( builder, "encrypt_step1" ) );

    gtk_widget_show( step1 );
}

G_MODULE_EXPORT void encrypt_step1_cancel( GtkWidget* button, gpointer data ) {
    GtkWidget* step1;
    step1 = GTK_WIDGET( gtk_builder_get_object( builder, "encrypt_step1" ) );

    gtk_widget_hide( step1 );
}

G_MODULE_EXPORT void encrypt_step1_ok( GtkWidget* button, gpointer data ) {
    // Make sure there's actually a file selected.
    GtkFileChooser* file;

    file = GTK_FILE_CHOOSER( gtk_builder_get_object( builder, "file_e1_select" ) );

    if ( gtk_file_chooser_get_filename( file ) == NULL )
        return;

    // This part of the reset is here because Glade won't let me set default values.
    GtkToggleButton* sha1;
    GtkToggleButton* sha224;
    GtkToggleButton* sha256;
    GtkToggleButton* sha384;
    GtkToggleButton* sha512;
    GtkToggleButton* md5;
    GtkToggleButton* cubehash;
    GtkToggleButton* whirlpool;
    GtkToggleButton* randomseed;
    GtkWidget* seed;

    sha1      = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha1" ) );
    sha224    = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha224" ) );
    sha256    = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha256" ) );
    sha384    = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha384" ) );
    sha512    = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha512" ) );
    md5       = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_md5" ) );
    cubehash  = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_cubehash" ) );
    whirlpool = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_whirlpool" ) );
    randomseed = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "radio_e2_randomseed_off" ) );
    seed = GTK_WIDGET( gtk_builder_get_object( builder, "hscale_e2_randomseed" ) );

    gtk_toggle_button_set_active( sha1, TRUE );
    gtk_toggle_button_set_active( sha224, FALSE );
    gtk_toggle_button_set_active( sha256, FALSE );
    gtk_toggle_button_set_active( sha384, FALSE );
    gtk_toggle_button_set_active( sha512, FALSE );
    gtk_toggle_button_set_active( md5, FALSE );
    gtk_toggle_button_set_active( cubehash, FALSE );
    gtk_toggle_button_set_active( whirlpool, FALSE );
    gtk_toggle_button_set_active( randomseed, TRUE );
    gtk_widget_set_sensitive( seed, FALSE );
    gtk_range_set_value( GTK_RANGE( seed ), 20.0 );

    // Now on to the actual displaying part.
    GtkWidget* step1;
    GtkWidget* step2;

    step1 = GTK_WIDGET( gtk_builder_get_object( builder, "encrypt_step1" ) );
    step2 = GTK_WIDGET( gtk_builder_get_object( builder, "encrypt_step2" ) );

    gtk_widget_show( step2 );
    gtk_widget_hide( step1 );
}

/* Password scoring method adapted from
 * http://www.geekwisdom.com/js/passwordmeter.js
 */
G_MODULE_EXPORT void encrypt_step2_passchange( GtkEntry* button, gpointer data ) {
    GtkProgressBar* meter;

    meter = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_e2_pass_strength" ) );

    int len = gtk_entry_get_text_length( button );
    const char* pass = gtk_entry_get_text( button );

    if ( len == 0 ) {
        gtk_progress_bar_set_fraction( meter, 0.0 );
        gtk_progress_bar_set_text( meter, "Enter a password." );

        return;
    }

    int score, i,
        lower = 0,
        upper = 0,
        num   = 0,
        symb  = 0;

    // Total length
    if ( len > 15 ) {
        score = 18;
    } else if ( len > 7 ) {
        score = 12;
    } else if ( len > 4 ) {
        score = 6;
    } else {
        score = 3;
    }

    for ( i = 0; i < len; i++ ) {
        if ( pass[i] >= 'a' && pass[i] <= 'z' )
            lower++;
        else if ( pass[i] >= 'A' && pass[i] <= 'Z' )
            upper++;
        else if ( pass[i] >= '0' && pass[i] <= '9' )
            num++;
        else
            symb++;
    }

    // Letters
    if ( lower || upper )
        score += 1;
    if ( lower && upper )
        score += 2;
    if ( lower + upper > 1 )
        score += 2;
    if ( lower > 1 && upper > 1 )
        score += 2;

    // Numbers
    if ( num == 1 )
        score += 5;
    if ( num > 1 )
        score += 7;

    // Symbols
    if ( symb == 1 )
        score += 5;
    if ( symb > 1 )
        score += 7;

    // Combinations
    if ( ( upper || lower ) && num )
        score++;
    if ( upper && lower )
        score++;
    if ( ( upper || lower ) && num && symb )
        score += 2;
    if ( upper && lower && num && symb )
        score += 2;

    gtk_progress_bar_set_fraction( meter, score / 45.0 );
    if ( score == 45 )
        gtk_progress_bar_set_text( meter, "Excellent" );
    else if ( score > 39 )
        gtk_progress_bar_set_text( meter, "Secure" );
    else if ( score > 24 )
        gtk_progress_bar_set_text( meter, "Good" );
    else if ( score > 9 )
        gtk_progress_bar_set_text( meter, "Ok" );
    else
        gtk_progress_bar_set_text( meter, "Bad" );
}

G_MODULE_EXPORT unsigned char encrypt_step2_get_algos() {
    unsigned char algos = '\0';

    GtkToggleButton *sha1,
                    *sha224,
                    *sha256,
                    *sha384,
                    *sha512,
                    *md5,
                    *cubehash,
                    *whirlpool;

    sha1      = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha1" ) );
    sha224    = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha224" ) );
    sha256    = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha256" ) );
    sha384    = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha384" ) );
    sha512    = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha512" ) );
    md5       = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_md5" ) );
    cubehash  = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_cubehash" ) );
    whirlpool = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_whirlpool" ) );

    if ( gtk_toggle_button_get_active( sha1 ) )
        algos |= '\x01';
    if ( gtk_toggle_button_get_active( sha224 ) )
        algos |= '\x02';
    if ( gtk_toggle_button_get_active( sha256 ) )
        algos |= '\x04';
    if ( gtk_toggle_button_get_active( sha384 ) )
        algos |= '\x08';
    if ( gtk_toggle_button_get_active( sha512 ) )
        algos |= '\x10';
    if ( gtk_toggle_button_get_active( md5 ) )
        algos |= '\x20';
    if ( gtk_toggle_button_get_active( cubehash ) )
        algos |= '\x40';
    if ( gtk_toggle_button_get_active( whirlpool ) )
        algos |= '\x80';

    return algos;
}

G_MODULE_EXPORT void encrypt_step2_check_hashalgo( GtkWidget* button, gpointer data ) {
    int seedlen = SHACrypt_GetLength( encrypt_step2_get_algos() );
    if ( !seedlen ) {
        GtkToggleButton* sha1;
        sha1 = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "check_e2_sha1" ) );

        gtk_toggle_button_set_active( sha1, TRUE );

        seedlen = 20;
    }

    GtkToggleButton* randomseed;
    GtkWidget* seed;

    randomseed = GTK_TOGGLE_BUTTON( gtk_builder_get_object( builder, "radio_e2_randomseed_off" ) );
    seed = GTK_WIDGET( gtk_builder_get_object( builder, "hscale_e2_randomseed" ) );

    if ( gtk_toggle_button_get_active( randomseed ) ) {
        gtk_widget_set_sensitive( seed, FALSE );

        gtk_range_set_value( GTK_RANGE( seed ), (gdouble)seedlen );
    } else {
        gtk_widget_set_sensitive( seed, TRUE );
    }
}

G_MODULE_EXPORT void encrypt_step3() {
    GtkWindow *step3;
    GtkEntry *pass1,
             *pass2;
    GtkFileChooser *file;
    GtkProgressBar *progress,
                   *meter;
    GtkRange *seedlen;
    unsigned char *password;
    unsigned int passlen;

    step3 = GTK_WINDOW( gtk_builder_get_object( builder, "encrypt_step3" ) );
    pass1 = GTK_ENTRY( gtk_builder_get_object( builder, "field_e2_password1" ) );
    pass2 = GTK_ENTRY( gtk_builder_get_object( builder, "field_e2_password2" ) );
    file = GTK_FILE_CHOOSER( gtk_builder_get_object( builder, "file_e1_select" ) );
    progress = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_e3_progress" ) );
    seedlen = GTK_RANGE( gtk_builder_get_object( builder, "hscale_e2_randomseed" ) );
    meter = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_e2_pass_strength" ) );

    gdk_threads_enter();
    password = gtk_entry_get_text( pass1 );
    passlen = gtk_entry_get_text_length( pass1 );
    gtk_progress_bar_set_text( progress, NULL );
    gtk_window_set_deletable( step3, FALSE );
    gtk_entry_set_text( pass1, "" );
    gtk_entry_set_text( pass2, "" );
    gtk_progress_bar_set_text( meter, "Enter a password" );
    gtk_progress_bar_set_fraction( meter, 0.0 );
    gdk_threads_leave();

    FILE *f,
         *o;
    unsigned long i, n,
                  size;
    SHACrypt_Context s;
    unsigned char algos = encrypt_step2_get_algos(),
                  fbuf[256] = {0},
                  ibuf[256] = {0},
                  obuf[256] = {0},
                  randkey[256] = {0},
                  scramblekey[256] = {0},
                  percentage[8] = {0},
                  *tmp,
                  *filename;

    filename = gtk_file_chooser_get_filename( file );

    n = (int)gtk_range_get_value( seedlen );

    f = fopen( filename, "rb" );

    tmp = g_strdup_printf( "%s.shacrypt", filename );

    i = 0;
    while ( ( o = fopen( tmp, "rb" ) ) != NULL ) {
        fclose( o );
        g_free( tmp );
        i++;
        tmp = g_strdup_printf( "%s_%lu.shacrypt", filename, i );
    }

    o = fopen( tmp, "wb" );
    g_free( tmp );

    SHACrypt_GenRand( randkey, n );

    SHACrypt_Init( &s, password, passlen, algos, randkey, n );

    fwrite( identifier, 1, 3, o );      // Identifier
    fwrite( "\3\0", 1, 2, o );          // Format version
    fputc( (unsigned char)(n - 1), o ); // Random seed length
    fwrite( randkey, 1, n, o );         // Random seed
    fputc( algos, o );                  // Hash alogithms used

    SHACrypt_GenRand( scramblekey, n );
    SHACrypt_SetScramble( &s, scramblekey, n );

    /* We need to also encrypt the scramble key */
    fbuf[0] = (unsigned char)(n - 1);
    SHACrypt_Process( &s, fbuf, obuf, 1 );
    fwrite( obuf, 1, 1, o );            // Scramble key length
    SHACrypt_Process( &s, scramblekey, obuf, n );
    fwrite( obuf, 1, n, o );            // Scramble key

    fseek( f, 0, SEEK_END );
    size = ftell( f );
    fseek( f, 0, SEEK_SET );

    i = 0;

    /* Do the actual encryption */
    while ( ( n = fread( fbuf, 1, 256, f ) ) > 0 ) {
        i += n;

        gdk_threads_enter();
        sprintf( percentage, "%.2f%%", (double)i / size * 100 );
        gtk_progress_bar_set_text( progress, percentage );
        gtk_progress_bar_set_fraction( progress, (double)i / size );
        gdk_threads_leave();

        SHACrypt_Scramble( &s, fbuf, ibuf, n );
        SHACrypt_Process( &s, ibuf, obuf, n );

        fwrite( obuf, 1, n, o );
    }

    fclose( f );
    fclose( o );

    gdk_threads_enter();
    gtk_progress_bar_set_text( progress, "Done!" );
    gtk_progress_bar_set_fraction( progress, 1.0 );
    gtk_window_set_deletable( step3, TRUE );
    gdk_threads_leave();
}

G_MODULE_EXPORT void encrypt_step2_ok( GtkWidget* button, gpointer data ) {
    GtkNotebook* tabs;
    GtkEntry*    pass1;
    GtkEntry*    pass2;

    tabs = GTK_NOTEBOOK( gtk_builder_get_object( builder, "notebook_e2_tabs" ) );
    pass1 = GTK_ENTRY( gtk_builder_get_object( builder, "field_e2_password1" ) );
    pass2 = GTK_ENTRY( gtk_builder_get_object( builder, "field_e2_password2" ) );

    if ( !gtk_entry_get_text_length( pass1 ) ) {
        gtk_notebook_set_current_page( tabs, 0 );
        gtk_widget_grab_focus( GTK_WIDGET( pass1 ) );

        return;
    }

    if ( !gtk_entry_get_text_length( pass2 ) ) {
        gtk_notebook_set_current_page( tabs, 0 );
        gtk_widget_grab_focus( GTK_WIDGET( pass2 ) );

        return;
    }

    if ( strcmp( gtk_entry_get_text( pass1 ), gtk_entry_get_text( pass2 ) ) == 0 ) {
        GtkWidget* step2;
        GtkWidget* step3;

        step2 = GTK_WIDGET( gtk_builder_get_object( builder, "encrypt_step2" ) );
        step3 = GTK_WIDGET( gtk_builder_get_object( builder, "encrypt_step3" ) );

        gtk_widget_show( step3 );
        gtk_widget_hide( step2 );
    
        g_thread_create( (GThreadFunc)encrypt_step3, NULL, FALSE, NULL );
    } else {
        GtkProgressBar* meter;

        meter = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_e2_pass_strength" ) );

        gtk_progress_bar_set_text( meter, "Passwords do not match!" );
    }
}

G_MODULE_EXPORT void encrypt_step2_cancel( GtkWidget *button, gpointer data ) {
    GtkWidget* step2;
    GtkEntry* pass1;
    GtkEntry* pass2;
    GtkProgressBar* meter;

    step2 = GTK_WIDGET( gtk_builder_get_object( builder, "encrypt_step2" ) );
    pass1 = GTK_ENTRY( gtk_builder_get_object( builder, "field_e2_password1" ) );
    pass2 = GTK_ENTRY( gtk_builder_get_object( builder, "field_e2_password2" ) );
    meter = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_e2_pass_strength" ) );

    gtk_widget_hide( step2 );

    gtk_entry_set_text( pass1, "" );
    gtk_entry_set_text( pass2, "" );
    gtk_progress_bar_set_text( meter, "Enter a password" );
    gtk_progress_bar_set_fraction( meter, 0.0 );
}

/* Decryption */
G_MODULE_EXPORT void button_pressed_decrypt( GtkWidget *button, gpointer data ) {
    GtkWidget* step1;
    step1 = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step1" ) );

    gtk_widget_show( step1 );
}

G_MODULE_EXPORT void decrypt_step1_ok( GtkWidget *button, gpointer data ) {
    GtkFileChooser *file;

    file = GTK_FILE_CHOOSER( gtk_builder_get_object( builder, "file_d1_select" ) );

    // Make sure there's actually a file selected.
    if ( gtk_file_chooser_get_filename( file ) == NULL )
        return;

    // Make sure it's an SHACrypt file.
    FILE *f;
    unsigned char fbuf[5];

    f = fopen( gtk_file_chooser_get_filename( file ), "rb" );

    if ( f == NULL || fread( fbuf, 1, 5, f ) != 5 )
        strcpy( fbuf, "fail." ); // Make sure we have all 5 bytes set.

    if ( f != NULL )
        fclose( f );

    if ( strncmp( fbuf, identifier, 3 ) != 0 || fbuf[3] < '\1' || fbuf[4] != '\0' ) {
        GtkWidget *invalid_file;

        invalid_file = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step1_error_notshacrypt" ) );

        gtk_widget_show( invalid_file );

        return;
    }

    if ( fbuf[3] > '\3' ) {
        GtkWidget *new_version;

        new_version = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step1_error_newversion" ) );

        gtk_widget_show( new_version );

        return;
    }

    GtkWidget *step1,
              *step2;

    step1 = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step1" ) );
    step2 = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step2" ) );

    gtk_widget_show( step2 );
    gtk_widget_hide( step1 );
}

G_MODULE_EXPORT void decrypt_step1_cancel( GtkWidget *button, gpointer data ) {
    GtkWidget *step1;
    step1 = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step1" ) );

    gtk_widget_hide( step1 );
}

G_MODULE_EXPORT void decrypt_step1_error1_ok( GtkWidget *button, gpointer data ) {
    GtkWidget *error;
    error = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step1_error_notshacrypt" ) );

    gtk_widget_hide( error );
}

G_MODULE_EXPORT void decrypt_step1_error2_ok( GtkWidget *button, gpointer data ) {
    GtkWidget *error;
    error = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step1_error_newversion" ) );

    gtk_widget_hide( error );
}

G_MODULE_EXPORT void decrypt_step3() {
    GtkWindow *step3;
    GtkEntry *pass;
    GtkFileChooser *file;
    GtkProgressBar *progress;

    step3 = GTK_WINDOW( gtk_builder_get_object( builder, "decrypt_step3" ) );
    pass = GTK_ENTRY( gtk_builder_get_object( builder, "field_d2_password" ) );
    file = GTK_FILE_CHOOSER( gtk_builder_get_object( builder, "file_d1_select" ) );
    progress = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_d3_progress" ) );

    gdk_threads_enter();
    gtk_progress_bar_set_text( progress, NULL );
    gtk_window_set_deletable( step3, FALSE );
    gdk_threads_leave();

    FILE *f,
         *o;
    unsigned long i, size, n, version;
    SHACrypt_Context s;
    unsigned char algos = '\0',
                  randlen = '\0',
                  fbuf[256] = {0},
                  ibuf[256] = {0},
                  obuf[256] = {0},
                  percentage[8] = {0},
                  randkey[256] = {0},
                  scramblekey[256] = {0},
                  scramblelen,
                  **filename = g_strsplit( gtk_file_chooser_get_filename( file ), ".", -1 ),
                  *filename_prefix,
                  *filename_suffix,
                  *tmp;

    f = fopen( g_strjoinv( ".", filename ), "rb" );

    i = g_strv_length( filename );

    if ( i == 2 ) {
        filename_suffix = "";
        filename_prefix = g_strdup( filename[0] );
    } else {
        n = strlen( filename[i - 1] ) + strlen( filename[i - 2] ) + 2;

        filename_suffix = g_strconcat( ".", filename[i - 2], NULL );

        tmp = g_strjoinv( ".", filename );
        filename_prefix = g_strndup( tmp, strlen( tmp ) - n );
        g_free( tmp );
    }
    g_strfreev( filename );

    tmp = g_strdup_printf( "%s%s", filename_prefix, filename_suffix );

    i = 0;
    while ( ( o = fopen( tmp, "rb" ) ) != NULL ) {
        fclose( o );
        g_free( tmp );
        i++;
        tmp = g_strdup_printf( "%s_%lu%s", filename_prefix, i, filename_suffix );
    }

    o = fopen( tmp, "wb" );

    g_free( tmp );
    g_free( filename_prefix );
    g_free( filename_suffix );

    fread( fbuf, 1, 5, f );

    if ( fbuf[3] == '\1' ) {
        randlen = '\x13';
        fread( randkey, 1, 20, f );
        algos = '\x01';
        version = 2;
    }
    if ( fbuf[3] == '\2' ) {
        randlen = fgetc( f );
        fread( randkey, 1, (int)randlen + 1, f );
        algos = fgetc( f );
        version = 2;
    }
    if ( fbuf[3] == '\3' ) {
        randlen = fgetc( f );
        fread( randkey, 1, (int)randlen + 1, f );
        algos = fgetc( f );
        version = 3;
    }

    SHACrypt_Init( &s, g_strdup( gtk_entry_get_text( pass ) ), gtk_entry_get_text_length( pass ), algos, randkey, (int)randlen + 1 );

    gtk_entry_set_text( pass, "" );

    i = ftell( f );
    fseek( f, 0, SEEK_END );
    size = ftell( f ) - i;
    fseek( f, i, SEEK_SET );

    /* Get the scramble key */
    if ( version >= 3 ) {
        n = fread( fbuf, 1, 1, f );
        size--;
        SHACrypt_Process( &s, fbuf, obuf, 1 );
        scramblelen = obuf[0];

        n = fread( fbuf, 1, (int)scramblelen + 1, f );
        size -= n;
        SHACrypt_Process( &s, fbuf, scramblekey, n );

        SHACrypt_SetScramble( &s, scramblekey, n );
    }

    i = 0;
    /* Do the actual encryption */
    while ( ( n = fread( fbuf, 1, 256, f ) ) > 0 ) {
        i += n;

        gdk_threads_enter();
        sprintf( percentage, "%.2f%%", (double)i / size * 100 );
        gtk_progress_bar_set_text( progress, percentage );
        gtk_progress_bar_set_fraction( progress, (double)i / size );
        gdk_threads_leave();

        if ( version >= 3 ) {
            SHACrypt_Process( &s, fbuf, ibuf, n );
            SHACrypt_Unscramble( &s, ibuf, obuf, n );
        } else {
            SHACrypt_Process( &s, fbuf, obuf, n );
        }

        fwrite( obuf, 1, n, o );
    }

    fclose( f );
    fclose( o );

    gdk_threads_enter();
    gtk_progress_bar_set_text( progress, "Done!" );
    gtk_progress_bar_set_fraction( progress, 1.0 );
    gtk_window_set_deletable( step3, TRUE );
    gdk_threads_leave();
}

G_MODULE_EXPORT void decrypt_step2_ok( GtkWidget* button, gpointer data ) {
    GtkEntry* pass;

    pass = GTK_ENTRY( gtk_builder_get_object( builder, "field_d2_password" ) );

    if ( !gtk_entry_get_text_length( pass ) ) {
        gtk_widget_grab_focus( GTK_WIDGET( pass ) );

        return;
    }

    GtkWidget *step2,
              *step3;

    step2 = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step2" ) );
    step3 = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step3" ) );

    gtk_widget_show( step3 );
    gtk_widget_hide( step2 );

    g_thread_create( (GThreadFunc)decrypt_step3, NULL, FALSE, NULL );
}

G_MODULE_EXPORT void decrypt_step2_cancel( GtkWidget *button, gpointer data ) {
    GtkWidget *step2;
    GtkEntry *pass;

    step2 = GTK_WIDGET( gtk_builder_get_object( builder, "decrypt_step2" ) );
    pass = GTK_ENTRY( gtk_builder_get_object( builder, "field_d2_password" ) );

    gtk_widget_hide( step2 );
    gtk_entry_set_text( pass, "" );
}

/* Asymmetric */
G_MODULE_EXPORT void button_pressed_asymmetric( GtkWidget *button, gpointer data ) {
    GtkWidget *step1;
    step1 = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step1" ) );

    gtk_widget_show( step1 );
}

G_MODULE_EXPORT void asymmetric_step1_cancel( GtkWidget *button, gpointer data ) {
    GtkWidget *step1;
    step1 = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step1" ) );

    gtk_widget_hide( step1 );
}

G_MODULE_EXPORT void asymmetric_step1_error1_ok( GtkWidget *button, gpointer data ) {
    GtkWidget *error;
    error = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step1_error_notshacrypt" ) );

    gtk_widget_hide( error );
}

G_MODULE_EXPORT void asymmetric_step1_error2_ok( GtkWidget *button, gpointer data ) {
    GtkWidget *error;
    error = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step1_error_newversion" ) );

    gtk_widget_hide( error );
}

G_MODULE_EXPORT void asymmetric_step1_ok( GtkWidget *button, gpointer data ) {
    GtkFileChooser *file;

    file = GTK_FILE_CHOOSER( gtk_builder_get_object( builder, "file_a1_select" ) );

    // Make sure there's actually a file selected.
    if ( gtk_file_chooser_get_filename( file ) == NULL )
        return;

    // Make sure it's an SHACrypt file.
    FILE *f;
    char fbuf[5];

    f = fopen( gtk_file_chooser_get_filename( file ), "rb" );

    if ( f == NULL || fread( fbuf, 1, 5, f ) != 5 )
        strcpy( fbuf, "fail." ); // Make sure we have all 5 bytes set.

    if ( f != NULL ) {
        fclose( f );
    }

    if ( strncmp( fbuf, identifier, 3 ) != 0 || fbuf[3] < '\1' || fbuf[4] != '\0' ) {
        GtkWidget *invalid_file;

        invalid_file = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step1_error_notshacrypt" ) );

        gtk_widget_show( invalid_file );

        return;
    }

    if ( fbuf[3] > '\3' ) {
        GtkWidget *new_version;

        new_version = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step1_error_newversion" ) );

        gtk_widget_show( new_version );

        return;
    }

    // Now on to the actual displaying part.
    GtkWidget* step1;
    GtkWidget* step2;

    step1 = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step1" ) );
    step2 = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step2" ) );

    gtk_widget_show( step2 );
    gtk_widget_hide( step1 );
}

/* Password scoring method adapted from
 * http://www.geekwisdom.com/js/passwordmeter.js
 */
G_MODULE_EXPORT void asymmetric_step2_passchange( GtkEntry* button, gpointer data ) {
    GtkProgressBar* meter;

    meter = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_a2_pass_strength" ) );

    int len = gtk_entry_get_text_length( button );
    const char* pass = gtk_entry_get_text( button );

    if ( len == 0 ) {
        gtk_progress_bar_set_fraction( meter, 0.0 );
        gtk_progress_bar_set_text( meter, "Enter a password." );

        return;
    }

    int score, i,
        lower = 0,
        upper = 0,
        num   = 0,
        symb  = 0;

    // Total length
    if ( len > 15 ) {
        score = 18;
    } else if ( len > 7 ) {
        score = 12;
    } else if ( len > 4 ) {
        score = 6;
    } else {
        score = 3;
    }

    for ( i = 0; i < len; i++ ) {
        if ( pass[i] >= 'a' && pass[i] <= 'z' )
            lower++;
        else if ( pass[i] >= 'A' && pass[i] <= 'Z' )
            upper++;
        else if ( pass[i] >= '0' && pass[i] <= '9' )
            num++;
        else
            symb++;
    }

    // Letters
    if ( lower || upper )
        score += 1;
    if ( lower && upper )
        score += 2;
    if ( lower + upper > 1 )
        score += 2;
    if ( lower > 1 && upper > 1 )
        score += 2;

    // Numbers
    if ( num == 1 )
        score += 5;
    if ( num > 1 )
        score += 7;

    // Symbols
    if ( symb == 1 )
        score += 5;
    if ( symb > 1 )
        score += 7;

    // Combinations
    if ( ( upper || lower ) && num )
        score++;
    if ( upper && lower )
        score++;
    if ( ( upper || lower ) && num && symb )
        score += 2;
    if ( upper && lower && num && symb )
        score += 2;

    gtk_progress_bar_set_fraction( meter, score / 45.0 );
    if ( score == 45 )
        gtk_progress_bar_set_text( meter, "Excellent" );
    else if ( score > 39 )
        gtk_progress_bar_set_text( meter, "Secure" );
    else if ( score > 24 )
        gtk_progress_bar_set_text( meter, "Good" );
    else if ( score > 9 )
        gtk_progress_bar_set_text( meter, "Ok" );
    else
        gtk_progress_bar_set_text( meter, "Bad" );
}

G_MODULE_EXPORT void asymmetric_step3() {
    GtkWindow *step3;
    GtkEntry *pass,
             *pass2;
    GtkFileChooser *file;
    GtkProgressBar *progress;

    step3 = GTK_WINDOW( gtk_builder_get_object( builder, "asymmetric_step3" ) );
    pass = GTK_ENTRY( gtk_builder_get_object( builder, "field_a2_password1" ) );
    pass2 = GTK_ENTRY( gtk_builder_get_object( builder, "field_a2_password2" ) );
    file = GTK_FILE_CHOOSER( gtk_builder_get_object( builder, "file_a1_select" ) );
    progress = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_a3_progress" ) );

    gdk_threads_enter();
    gtk_progress_bar_set_text( progress, NULL );
    gtk_window_set_deletable( step3, FALSE );
    gdk_threads_leave();

    FILE *f,
         *o;
    unsigned long i, size, n;
    SHACrypt_Context s;
    unsigned char algos = '\0',
                  randlen = '\0',
                  fbuf[256] = {0},
                  obuf[256] = {0},
                  percentage[8] = {0},
                  *filename = gtk_file_chooser_get_filename( file ),
                  *tmp,
                  *version;

    f = fopen( filename, "rb" );

    filename = g_strndup( filename, strlen( filename ) - 9 );

    tmp = g_strdup_printf( "%s_1.shacrypt", filename );

    i = 1;
    while ( ( o = fopen( tmp, "rb" ) ) != NULL ) {
        fclose( o );
        g_free( tmp );
        i++;
        tmp = g_strdup_printf( "%s_%lu.shacrypt", filename, i );
    }

    o = fopen( tmp, "wb" );

    g_free( tmp );
    g_free( filename );

    fread( fbuf, 1, 5, f );

    if ( fbuf[3] == '\1' ) {
        randlen = '\x13';
        fread( fbuf, 1, 20, f );
        algos = '\x01';
        version = "\2\0";
    }
    if ( fbuf[3] == '\2' ) {
        randlen = fgetc( f );
        fread( fbuf, 1, (int)randlen + 1, f );
        algos = fgetc( f );
        version = "\2\0";
    }
    if ( fbuf[3] == '\3' ) {
        randlen = fgetc( f );
        fread( fbuf, 1, (int)randlen + 1, f );
        algos = fgetc( f );
        version = "\3\0";
    }

    fwrite( identifier, 1, 3, o );
    fwrite( version, 1, 2, o );
    fputc( randlen, o );
    fwrite( fbuf, 1, (int)randlen + 1, o );
    fputc( algos, o );

    SHACrypt_Init( &s, g_strdup( gtk_entry_get_text( pass ) ), gtk_entry_get_text_length( pass ), algos, fbuf, (int)randlen + 1 );

    gtk_entry_set_text( pass, "" );
    gtk_entry_set_text( pass2, "" );

    i = ftell( f );
    fseek( f, 0, SEEK_END );
    size = ftell( f ) - i;
    fseek( f, i, SEEK_SET );

    i = 0;
    /* Do the actual encryption */
    while ( ( n = fread( fbuf, 1, 256, f ) ) > 0 ) {
        i += n;

        gdk_threads_enter();
        sprintf( percentage, "%.2f%%", (double)i / size * 100 );
        gtk_progress_bar_set_text( progress, percentage );
        gtk_progress_bar_set_fraction( progress, (double)i / size );
        gdk_threads_leave();

        SHACrypt_Process( &s, fbuf, obuf, n );

        fwrite( obuf, 1, n, o );
    }

    fclose( f );
    fclose( o );

    gdk_threads_enter();
    gtk_progress_bar_set_text( progress, "Done!" );
    gtk_progress_bar_set_fraction( progress, 1.0 );
    gtk_window_set_deletable( step3, TRUE );
    gdk_threads_leave();
}

G_MODULE_EXPORT void asymmetric_step2_ok( GtkWidget* button, gpointer data ) {
    GtkEntry* pass1;
    GtkEntry* pass2;

    pass1 = GTK_ENTRY( gtk_builder_get_object( builder, "field_a2_password1" ) );
    pass2 = GTK_ENTRY( gtk_builder_get_object( builder, "field_a2_password2" ) );

    if ( !gtk_entry_get_text_length( pass1 ) ) {
        gtk_widget_grab_focus( GTK_WIDGET( pass1 ) );

        return;
    }

    if ( !gtk_entry_get_text_length( pass2 ) ) {
        gtk_widget_grab_focus( GTK_WIDGET( pass2 ) );

        return;
    }

    if ( strcmp( gtk_entry_get_text( pass1 ), gtk_entry_get_text( pass2 ) ) == 0 ) {
        GtkWidget* step2;
        GtkWidget* step3;

        step2 = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step2" ) );
        step3 = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step3" ) );

        gtk_widget_show( step3 );
        gtk_widget_hide( step2 );

        g_thread_create( (GThreadFunc)asymmetric_step3, NULL, FALSE, NULL );
    } else {
        GtkProgressBar* meter;

        meter = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_a2_pass_strength" ) );

        gtk_progress_bar_set_text( meter, "Passwords do not match!" );
    }
}

G_MODULE_EXPORT void asymmetric_step2_cancel( GtkWidget* button, gpointer data ) {
    GtkWidget* step2;
    GtkEntry* pass1;
    GtkEntry* pass2;
    GtkProgressBar* meter;

    step2 = GTK_WIDGET( gtk_builder_get_object( builder, "asymmetric_step2" ) );
    pass1 = GTK_ENTRY( gtk_builder_get_object( builder, "field_a2_password1" ) );
    pass2 = GTK_ENTRY( gtk_builder_get_object( builder, "field_a2_password2" ) );
    meter = GTK_PROGRESS_BAR( gtk_builder_get_object( builder, "progressbar_a2_pass_strength" ) );

    gtk_widget_hide( step2 );

    gtk_entry_set_text( pass1, "" );
    gtk_entry_set_text( pass2, "" );
    gtk_progress_bar_set_text( meter, "Enter a password" );
    gtk_progress_bar_set_fraction( meter, 0.0 );
}

int main( int argc, char** argv ) {
    GtkWidget *window;
    GtkWindow *tempwindow;
    GError    *error = NULL;
    GdkPixbuf *icon;

    gtk_init( &argc, &argv );

    if ( !g_thread_supported() )
        g_thread_init( NULL );

    builder = gtk_builder_new();

    if( !gtk_builder_add_from_string( builder, shacrypt_glade, -1, &error ) ) {
        g_warning( "%s", error->message );
        g_free( error );
        return 1;
    }

    g_free( error );

    window = GTK_WIDGET( gtk_builder_get_object( builder, "main" ) );

    gtk_builder_connect_signals( builder, NULL );

    icon = gdk_pixbuf_from_pixdata( &shacrypt_icon, TRUE, NULL );
    gtk_window_set_icon( GTK_WINDOW( window ), icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "encrypt_step1" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "encrypt_step2" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "encrypt_step3" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "decrypt_step1" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "decrypt_step2" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "decrypt_step3" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "asymmetric_step1" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "asymmetric_step2" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "asymmetric_step3" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "decrypt_step1_error_notshacrypt" ) );
    gtk_window_set_icon( tempwindow, icon );

    tempwindow = GTK_WINDOW( gtk_builder_get_object( builder, "asymmetric_step1_error_notshacrypt" ) );
    gtk_window_set_icon( tempwindow, icon );

    GtkFileFilter* shacrypt_mask;
    shacrypt_mask = GTK_FILE_FILTER( gtk_builder_get_object( builder, "filter_shacrypt_ext" ) );

    gtk_file_filter_add_pattern( shacrypt_mask, "*.shacrypt" );

    gtk_widget_show( window );

    gtk_main();

    return 0;
}
